﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using tiDE;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

// Program to generate a table file for the assembler

namespace GenerateTable
{
    class Program
    {
        static void Main(string[] args)
        {
            List<OPCode> OPCodes = new List<OPCode>();
            Console.WriteLine("Mnomic Table Inputter");
            Console.WriteLine("Type an instruction mnomic code to add it.\nOther commands:");
            Console.WriteLine("/list: List inputted instructions");
            Console.WriteLine("/quit: quit");
            Console.WriteLine("/sort: move the immediate value opcodes to the end of the list");
            Console.WriteLine("/clear: Clear the console");
            Console.WriteLine("/reorder: Open the reorder window");
            Console.WriteLine("/remove [mnomic]: Removes the specified mnomic");
            BinaryFormatter format = new BinaryFormatter();
            if (File.Exists("z80.tab"))
            {
                StreamReader reader = new StreamReader("z80.tab");
                OPCodes = (List<OPCode>)format.Deserialize(reader.BaseStream);
                reader.Close();
                Console.WriteLine("Previous table loaded: " + OPCodes.Count.ToString() + " instructions");
            }
            LoadHardwareOperands();
            List<OPCode> newList = new List<OPCode>();
            for (int i = 0; i < OPCodes.Count; i++)
            {
                byte size = 0;
                char lastC = ' ';
                if (OPCodes[i].Match.Contains("$H1"))
                {
                    OPCode newCode = new OPCode();
                    OPCodes[i].Match = OPCodes[i].Match.Replace("+.^1.", "-.^1.");
                    OPCodes[i].Match = OPCodes[i].Match.Replace("-.*1.", "+.*1.");
                    newCode.Match = OPCodes[i].Match.Replace("+.*1.", "");
                    newCode.Match = newCode.Match.Replace("-.^1.", "");
                    newCode.Match = newCode.Match.Replace("-.^1.", "");
                    newCode.Value = OPCodes[i].Value.Replace("*1", "00000000");
                    newCode.Value = newCode.Value.Replace("^1", "00000000");
                    newList.Add(newCode);
                }
                foreach (char c in OPCodes[i].Value)
                {
                    if (lastC == '$')
                    {
                        size--;
                        size += (byte)OperandTable["$" + c][0].Value.Length;
                    }
                    else
                    {
                        if (c == '&' || c == '^' || c == '*' || c == '#')
                            size += 8;
                        if (c == '%')
                            size += 16;
                        if (c == '1' || c == '0')
                            size++;
                    }
                    lastC = c;
                }
                size /= 8;
                if (OPCodes[i].Match.ToLower().Contains("jr"))
                    size = 2;
                OPCodes[i].Size = size;
            }
            //OPCodes.AddRange(newList);
            StreamWriter writer = new StreamWriter("z80.tab");
            format.Serialize(writer.BaseStream, OPCodes);
            writer.Close();
            string input = "";
            while (input != "/quit")
            {
                input = Console.ReadLine();
                if (input == "/quit")
                {
                }
                else if (input == "/reorder")
                {
                    GUIManager manager = new GUIManager(OPCodes);
                    manager.ShowDialog();
                    Console.WriteLine("Accept new list? (Y/N)");
                    char c = Console.ReadKey(true).KeyChar.ToString().ToLower()[0];
                    if (c == 'y')
                    {
                        OPCodes = manager.Opcodes;
                        writer = new StreamWriter("z80.tab");
                        format.Serialize(writer.BaseStream, OPCodes);
                        writer.Close();
                    }
                }
                else if (input == "/list")
                {
                    foreach (OPCode opcode in OPCodes)
                        Console.WriteLine(opcode.Match + "\t:\t" + opcode.Value);
                }
                else if (input.StartsWith("/remove "))
                {
                    OPCode toRemove = null;
                    foreach (OPCode opcode in OPCodes)
                    {
                        if (input.Contains(opcode.Match))
                        {
                            toRemove = opcode;
                            break;
                        }
                    }
                    if (toRemove == null)
                        Console.WriteLine("OPCode not found");
                    else
                        OPCodes.Remove(toRemove);
                    writer = new StreamWriter("z80.tab");
                    format.Serialize(writer.BaseStream, OPCodes);
                    writer.Close();
                }
                else if (input == "/clear")
                {
                    Console.Clear();
                }
                else if (input == "/sort")
                {
                    List<OPCode> newOpCodes = new List<OPCode>();

                    foreach (OPCode opcode in OPCodes)
                        if (!opcode.Match.Contains("%") && !opcode.Match.Contains("&")
                            && !opcode.Match.Contains("*") && !opcode.Match.Contains("^"))
                            newOpCodes.Add(opcode);
                    foreach (OPCode opcode in OPCodes)
                        if (opcode.Match.Contains("%") || opcode.Match.Contains("&")
                            || opcode.Match.Contains("*") || opcode.Match.Contains("^"))
                            newOpCodes.Add(opcode);

                    OPCodes = newOpCodes;

                    newOpCodes = new List<OPCode>();
                    foreach (OPCode opcode in OPCodes)
                        if (opcode.Match.Contains("$H"))
                            newOpCodes.Add(opcode);
                    foreach (OPCode opcode in OPCodes)
                        if (!opcode.Match.Contains("$H"))
                            newOpCodes.Add(opcode);

                    OPCodes = newOpCodes;

                    writer = new StreamWriter("z80.tab");
                    format.Serialize(writer.BaseStream, OPCodes);
                    writer.Close();
                }
                else
                {
                    OPCode newOpcode = new OPCode();
                    newOpcode.Match = input;
                    Console.Write("Value: ");
                    newOpcode.Value = Console.ReadLine();
                    newOpcode.Size = 1;
                    if (newOpcode.Match.Contains("%"))
                        newOpcode.Size += 2;
                    if (newOpcode.Match.Contains("&"))
                        newOpcode.Size++;
                    OPCodes.Add(newOpcode);
                    writer = new StreamWriter("z80.tab");
                    format.Serialize(writer.BaseStream, OPCodes);
                    writer.Close();
                }
            }
        }

        /// <summary>
        /// Defines an operand and its value in binary, such as HL.
        /// </summary>
        struct HardwareOperand
        {
            public string Operand, Value;

            public HardwareOperand(string Operand, string Value)
            {
                this.Operand = Operand;
                this.Value = Value;
            }
        }

        static Dictionary<string, HardwareOperand[]> OperandTable;

        /// <summary>
        /// Loads the hardware operand constants
        /// </summary>
        private static void LoadHardwareOperands()
        {
            OperandTable = new Dictionary<string, HardwareOperand[]>();
            HardwareOperand[] Condition2Bit = new HardwareOperand[4];
            Condition2Bit[0] = new HardwareOperand("NZ", "00");
            Condition2Bit[1] = new HardwareOperand("Z", "01");
            Condition2Bit[2] = new HardwareOperand("NC", "10");
            Condition2Bit[3] = new HardwareOperand("C", "11");
            OperandTable.Add("$A", Condition2Bit);

            HardwareOperand[] Condition3Bit = new HardwareOperand[8];
            Condition3Bit[0] = new HardwareOperand("NZ", "000");
            Condition3Bit[1] = new HardwareOperand("Z", "001");
            Condition3Bit[2] = new HardwareOperand("NC", "010");
            Condition3Bit[3] = new HardwareOperand("C", "011");
            Condition3Bit[4] = new HardwareOperand("PO", "100");
            Condition3Bit[5] = new HardwareOperand("PE", "101");
            Condition3Bit[6] = new HardwareOperand("P", "110");
            Condition3Bit[7] = new HardwareOperand("M", "111");
            OperandTable.Add("$B", Condition3Bit);

            HardwareOperand[] Register8Bit3Bit = new HardwareOperand[8];
            Register8Bit3Bit[0] = new HardwareOperand("B", "000");
            Register8Bit3Bit[1] = new HardwareOperand("C", "001");
            Register8Bit3Bit[2] = new HardwareOperand("D", "010");
            Register8Bit3Bit[3] = new HardwareOperand("E", "011");
            Register8Bit3Bit[4] = new HardwareOperand("H", "100");
            Register8Bit3Bit[5] = new HardwareOperand("L", "101");
            Register8Bit3Bit[6] = new HardwareOperand("(HL)", "110");
            Register8Bit3Bit[7] = new HardwareOperand("A", "111");
            OperandTable.Add("$C", Register8Bit3Bit);

            HardwareOperand[] Register16Bit1Bit = new HardwareOperand[2];
            Register16Bit1Bit[0] = new HardwareOperand("BC", "0");
            Register16Bit1Bit[1] = new HardwareOperand("DE", "1");
            OperandTable.Add("$D", Register16Bit1Bit);

            HardwareOperand[] Register8Bit1Bit = new HardwareOperand[2];
            Register8Bit1Bit[0] = new HardwareOperand("I", "0");
            Register8Bit1Bit[1] = new HardwareOperand("R", "1");
            OperandTable.Add("$E", Register8Bit1Bit);

            HardwareOperand[] Register16Bit2Bit = new HardwareOperand[4];
            Register16Bit2Bit[0] = new HardwareOperand("BC", "00");
            Register16Bit2Bit[1] = new HardwareOperand("DE", "01");
            Register16Bit2Bit[2] = new HardwareOperand("HL", "10");
            Register16Bit2Bit[3] = new HardwareOperand("SP", "11");
            OperandTable.Add("$F", Register16Bit2Bit);

            HardwareOperand[] Register16Bit2BitAlt = new HardwareOperand[4];
            Register16Bit2BitAlt[0] = new HardwareOperand("BC", "00");
            Register16Bit2BitAlt[1] = new HardwareOperand("DE", "01");
            Register16Bit2BitAlt[2] = new HardwareOperand("HL", "10");
            Register16Bit2BitAlt[3] = new HardwareOperand("AF", "11");
            OperandTable.Add("$G", Register16Bit2BitAlt);

            HardwareOperand[] IndexRegisters8Bit = new HardwareOperand[2];
            IndexRegisters8Bit[0] = new HardwareOperand("IX", "11011101");
            IndexRegisters8Bit[1] = new HardwareOperand("IY", "11111101");
            OperandTable.Add("$H", IndexRegisters8Bit);

            HardwareOperand[] RST3Bit = new HardwareOperand[8];
            RST3Bit[0] = new HardwareOperand("00", "000");
            RST3Bit[1] = new HardwareOperand("08", "001");
            RST3Bit[2] = new HardwareOperand("10", "010");
            RST3Bit[3] = new HardwareOperand("18", "011");
            RST3Bit[4] = new HardwareOperand("20", "100");
            RST3Bit[5] = new HardwareOperand("28", "101");
            RST3Bit[6] = new HardwareOperand("30", "110");
            RST3Bit[7] = new HardwareOperand("38", "111");
            OperandTable.Add("$I", RST3Bit);
        }
    }
}
